// <copyright file="PlainLiteral.cs" company="Alan Dean">
// The MIT License

// Copyright (c) 2008 Alan Dean

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// </copyright>
// <author>Alan Dean</author>
// <email>alan.dean@thoughtpad.net</email>

namespace Semantic.Rdf
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Security.Permissions;
    using System.Xml;

    /// <summary>
    /// Represents an RDF typed literal.
    /// <para>This class cannot be inherited.</para>
    /// </summary>
    /// 
    /// <remarks>
    /// A <see cref="T:Semantic.Rdf.IPlainLiteral">plain literal</see> is a string combined with an optional language tag.
    /// This may be used for plain text in a natural language.
    /// As recommended in the <see href="http://www.w3.org/TR/rdf-concepts/#ref-rdf-semantics">RDF formal semantics</see>,
    /// these plain literals are self-denoting.
    /// </remarks>
    [Serializable]
    public sealed class PlainLiteral : IComparable, ISerializable, IPlainLiteral
    {
        #region fields

        /// <summary>
        /// Stores the <see cref="P:Semantic.Rdf.PlainLiteral.Language"/> data.
        /// </summary>
        private CultureInfo _language;

        /// <summary>
        /// Stores the <see cref="P:Semantic.Rdf.PlainLiteral.LexicalForm"/> data.
        /// </summary>
        private string _lexicalForm;
        
        #endregion

        #region public PlainLiteral(string, CultureInfo)
        /// <overloads>
        /// Overloaded.
        /// Initializes a new instance of the <see cref="T:Semantic.Rdf.PlainLiteral"/> class.
        /// </overloads>
        ///
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Semantic.Rdf.PlainLiteral"/> class
        /// with the specified <paramref name="lexicalForm"/> and <paramref name="language"/>.
        /// </summary>
        ///
        /// <param name="lexicalForm">The <see langword="string"/> lexical form.</param>
        /// <param name="language">The language value.</param>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="lexicalForm"/> is a <see langword="null"/> reference.<br/>-or-<br/>
        /// <paramref name="language"/> is a <see langword="null"/> reference.
        /// </exception>
        /// <exception cref="T:System.FormatException">
        /// <paramref name="value"/> is not in a correct format.
        /// </exception>
        public PlainLiteral(string lexicalForm, CultureInfo language)
        {
            this.LexicalForm = lexicalForm;
            this.Language = language;
        }
        #endregion

        #region private PlainLiteral(SerializationInfo, StreamingContext)
        /// <summary>
        /// Initializes a new instance of the <see cref="T:Semantic.Rdf.PlainLiteral"/> class
        /// from the specified instances of the <see cref="T:System.Runtime.Serialization.SerializationInfo"/>
        /// and <see cref="T:System.Runtime.Serialization.StreamingContext"/> classes.
        /// </summary>
        /// 
        /// <param name="info">
        /// An instance of the  <see cref="T:System.Runtime.Serialization.SerializationInfo"/> class
        /// containing the information required to serialize the
        /// new <see cref="T:Semantic.Rdf.PlainLiteral"/> instance. 
        /// </param>
        /// <param name="context">
        /// An instance of the <see cref="T:System.Runtime.Serialization.StreamingContext"/> class
        /// containing the source of the serialized stream associated with the
        /// new <see cref="T:Semantic.Rdf.PlainLiteral"/> instance.
        /// </param>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="info"/> is a <see langword="null"/> reference.
        /// </exception>
        private PlainLiteral(SerializationInfo info, StreamingContext context)
        {
            if (null == info)
            {
                throw new ArgumentNullException("info");
            }

            this.LexicalForm = info.GetString("_lexicalForm");
            this.Language = new CultureInfo(info.GetString("_language"));
        }
        #endregion

        #region public CultureInfo Language
        /// <summary>
        /// Gets the language tag.
        /// </summary>
        /// 
        /// <value>
        /// A a language tag as defined by <see href="http://www.w3.org/TR/rdf-concepts/#ref-rfc-3066">RFC-3066</see>,
        /// normalized to lowercase.
        /// </value>
        /// 
        /// <remarks>
        /// <para>
        /// <strong>Note:</strong>
        /// Literals in which the lexical form begins with a composing character
        /// (as defined by <see href="http://www.w3.org/TR/rdf-concepts/#ref-charmod">CHARMOD</see>)
        /// are allowed however they may cause interoperability problems, particularly with
        /// <see href="http://www.w3.org/TR/rdf-concepts/#ref-xml-1-1">XML version 1.1</see>.
        /// </para>
        /// <para>
        /// <strong>Note:</strong>
        /// When using the language tag, care must be taken not to confuse language with locale.
        /// The language tag relates only to human language text.
        /// Presentational issues should be addressed in end-user applications.
        /// </para>
        /// <para>
        /// <strong>Note:</strong>
        /// The case normalization of language tags is part of the description of the abstract syntax,
        /// and consequently the abstract behaviour of RDF applications.
        /// It does not constrain an RDF implementation to actually normalize the case.
        /// Crucially, the result of comparing two language tags should not be sensitive to the case of the original input.
        /// </para>
        /// </remarks>
        public CultureInfo Language
        {
            get
            {
                return this._language;
            }

            private set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }

                this._language = value;
            }
        }
        #endregion

        #region public string LexicalForm
        /// <summary>
        /// Gets the <see href="http://www.w3.org/TR/rdf-concepts/#dfn-lexical-form">lexical form</see>.
        /// </summary>
        /// 
        /// <value>
        /// A <see href="http://www.w3.org/TR/rdf-concepts/#ref-unicode">Unicode</see>
        /// <see langword="string"/>.
        /// </value>
        /// 
        /// <remarks>
        /// All literals have a lexical form being a <see href="http://www.w3.org/TR/rdf-concepts/#ref-unicode">Unicode</see> string,
        /// which SHOULD be in <see href="http://www.w3.org/TR/rdf-concepts/#ref-nfc">Normal Form C [NFC]</see>.
        /// </remarks>
        public string LexicalForm
        {
            get
            {
                return this._lexicalForm;
            }

            private set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("value");
                }

                this._lexicalForm = value;
            }
        }
        #endregion

        #region public static bool operator ==(PlainLiteral, PlainLiteral)
        /// <summary>Implements the equality operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the values of the operands are equal;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The equality operator is strongly-typed.</remarks>
        public static bool operator ==(PlainLiteral operand1, PlainLiteral operand2)
        {
            return (0 == PlainLiteral.Compare(operand1, operand2));
        }
        #endregion

        #region public static bool operator !=(PlainLiteral, PlainLiteral)
        /// <summary>Implements the inequality operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the values of its operands are not equal;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The inequality operator is strongly-typed.</remarks>
        public static bool operator !=(PlainLiteral operand1, PlainLiteral operand2)
        {
            return (0 != PlainLiteral.Compare(operand1, operand2));
        }
        #endregion

        #region public static bool operator <(PlainLiteral, PlainLiteral)
        /// <summary>Implements the less than operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the first operand is less than the second operand;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The operator is strongly-typed.</remarks>
        public static bool operator <(PlainLiteral operand1, PlainLiteral operand2)
        {
            return (PlainLiteral.Compare(operand1, operand2) < 0);
        }
        #endregion

        #region public static bool operator >(PlainLiteral, PlainLiteral)
        /// <summary>Implements the greater than operator for this type.</summary>
        /// 
        /// <param name="operand1">The first operand.</param>
        /// <param name="operand2">The second operand.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the first operands is greater than the second operand;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        /// 
        /// <remarks>The operator is strongly-typed.</remarks>
        public static bool operator >(PlainLiteral operand1, PlainLiteral operand2)
        {
            return (PlainLiteral.Compare(operand1, operand2) > 0);
        }
        #endregion

        #region public static int Compare(PlainLiteral, PlainLiteral)
        /// <summary>Compares two instances of this type.</summary>
        /// 
        /// <param name="comparand1">The first comparand.</param>
        /// <param name="comparand2">The second comparand.</param>
        /// 
        /// <returns>
        /// A 32-bit signed integer indicating the lexical relationship between the two comparands.
        /// <list type="table">
        /// <listheader>
        ///   <term>Value Type</term>
        ///   <description>Condition</description>
        /// </listheader>
        /// <item>
        ///   <term>Less than zero</term>
        ///   <description><paramref name="comparand1"/> is less than <paramref name="comparand2"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Zero</term>
        ///   <description><paramref name="comparand1"/> equals <paramref name="comparand2"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Greater than zero</term>
        ///   <description><paramref name="comparand1"/> is greater than <paramref name="comparand2"/>.</description>
        /// </item>
        /// </list>
        /// </returns>
        public static int Compare(PlainLiteral comparand1, PlainLiteral comparand2)
        {
            return object.ReferenceEquals(comparand1, comparand2)
                ? 0
                : string.Compare(
                    object.ReferenceEquals(null, comparand1) ? null as string : comparand1.ToString(),
                    object.ReferenceEquals(null, comparand2) ? null as string : comparand2.ToString(),
                    StringComparison.OrdinalIgnoreCase);
        }
        #endregion

        #region public override bool Equals(object)
        /// <summary>
        /// Determines if the specified <paramref name="obj"/> is equal to this instance..
        /// </summary>
        /// 
        /// <param name="obj">The <see langword="object"/> to compare with this instance.</param>
        /// 
        /// <returns>
        /// Returns <see langword="true"/> if the specified <paramref name="obj"/> is equal to this instance;
        /// otherwise <see langword="false"/>.
        /// </returns>
        public override bool Equals(object obj)
        {
            bool result = false;

            if (!object.ReferenceEquals(null, obj))
            {
                if (object.ReferenceEquals(this, obj))
                {
                    result = true;
                }
                else
                {
                    PlainLiteral cast = (obj as PlainLiteral);

                    if (!object.ReferenceEquals(null, cast))
                    {
                        result = (0 == PlainLiteral.Compare(this, cast));
                    }
                }
            }

            return result;
        }
        #endregion

        #region public override int GetHashCode()
        /// <summary>Returns the hash code for this instance.</summary>
        /// <returns>A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }
        #endregion

        #region public override string ToString()
        /// <summary>
        /// Returns a <see langword="string"/> representation of this instance.
        /// </summary>
        /// <returns>A <see langword="string"/> representation.</returns>
        [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Language strings should be lower cased.")]
        public override string ToString()
        {
            string value = null;

            if (CultureInfo.InvariantCulture == this.Language)
            {
                value = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", this.LexicalForm);
            }
            else
            {
                value = string.Format(CultureInfo.InvariantCulture, "\"{0}\"@{1}", this.LexicalForm, this.Language.Name.ToLowerInvariant());
            }

            return value;
        }
        #endregion

        #region public int CompareTo(object)
        /// <summary>
        /// Compares the current instance with another <see langword="object"/> of the same type.
        /// </summary>
        /// 
        /// <param name="obj">An <see langword="object"/> to compare with this instance.</param>
        /// 
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared.
        /// The return value has these meanings:
        /// <list type="table">
        /// <listheader>
        ///   <term>Value Type</term>
        ///   <description>Meaning</description>
        /// </listheader>
        /// <item>
        ///   <term>Less than zero</term>
        ///   <description>This instance is less than <paramref name="obj"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Zero</term>
        ///   <description>This instance is equal to <paramref name="obj"/>.</description>
        /// </item>
        /// <item>
        ///   <term>Greater than zero</term>
        ///   <description>This instance is greater than <paramref name="obj"/>.</description>
        /// </item>
        /// </list>
        /// </returns>
        /// 
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="obj"/> is not an instance of the
        /// <see cref="T:Semantic.Rdf.PlainLiteral"/> type.
        /// </exception>
        public int CompareTo(object obj)
        {
            int comparison = 1;

            if (!object.ReferenceEquals(null, obj))
            {
                PlainLiteral value = (obj as PlainLiteral);

                if (object.ReferenceEquals(null, value))
                {
                    throw new ArgumentException(
                        string.Format(CultureInfo.InvariantCulture, Properties.Resources.ObjectMustBeOfTypeException_Message_Format, "PlainLiteral"),
                        "obj");
                }

                comparison = PlainLiteral.Compare(this, value);
            }

            return comparison;
        }
        #endregion

        #region void ISerializable.GetObjectData(SerializationInfo, StreamingContext)
        /// <summary>Returns the data needed to serialize the current instance.</summary>
        /// 
        /// <param name="info">
        /// A <see cref="T:System.Runtime.Serialization.SerializationInfo"/> object containing the information
        /// required to serialize the <see cref="T:Semantic.Rdf.PlainLiteral"/>.
        /// </param>
        /// <param name="context">
        /// A <see cref="T:System.Runtime.Serialization.StreamingContext"/> object containing the source and destination
        /// of the serialized stream associated with the <see cref="T:Semantic.Rdf.PlainLiteral"/>.
        /// </param>
        /// 
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="info"/> is a <see langword="null"/> reference.
        /// </exception>
        [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            if (null == info)
            {
                throw new ArgumentNullException("info");
            }

            info.AddValue("_lexicalForm", this.LexicalForm);
            info.AddValue("_language", this.Language.Name);
        }
        #endregion
    }
}